package net.aiion.weave.impl.xml;

import java.util.Stack;

import net.aiion.weave.spi.xml.XmlEventFactory;
import net.aiion.weave.spi.xml.XmlException;
import net.aiion.weave.spi.xml.XmlGenerator;
import net.aiion.weave.spi.xml.XmlQualifiedName;
import net.aiion.weave.spi.xml.events.DocumentTypeDefinitionXmlEvent;

public class XmlGeneratorImpl extends AbstractXmlSource implements XmlGenerator
{
    private final XmlEventFactory xmlEventFactory;

    private final Stack<XmlQualifiedName> openElements;

    public XmlGeneratorImpl() {
        xmlEventFactory = XmlEventFactory.getInstance();
        openElements = new Stack<>();
    }

    @Override
    public XmlQualifiedName createQualifiedName(final String aNamespaceURI, final String aLocalName) {
        return xmlEventFactory.createQualifiedName(aNamespaceURI, aLocalName);
    }

    @Override
    public XmlQualifiedName createQualifiedName(final String aNamespaceURI, final String aLocalName,
        final String aPrefix)
    {
        return xmlEventFactory.createQualifiedName(aNamespaceURI, aLocalName, aPrefix);
    }

    @Override
    public void startDocument(final String aVersion, final String aEncoding, final boolean aStandalone) {
        dispatchXmlEvent(xmlEventFactory.createDocumentStart(aVersion, aEncoding, aStandalone));
    }

    @Override
    public void declareDocumentType(final int aDocumentTypeDeclarationId) {
        switch (aDocumentTypeDeclarationId) {
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_HTML5:
            declareHTML5DocumentType();
            break;
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_HTML401_STRICT:
            declareHTML401StrictDocumentType();
            break;
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_HTML401_TRANSITIONAL:
            declareHTML401TransitionalDocumentType();
            break;
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_HTML401_FRAMESET:
            declareHTML401FramesetDocumentType();
            break;
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_XHTML10_STRICT:
            declareXHTML10StrictDocumentType();
            break;
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_XHTML10_TRANSITIONAL:
            declareXHTML10TransitionalDocumentType();
            break;
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_XHTML10_FRAMESET:
            declareXHTML10FramesetDocumentType();
            break;
        case DocumentTypeDefinitionXmlEvent.DOCTYPE_XHTML11:
            declareXHTML11DocumentType();
            break;
        default:
            throw new XmlException("Unsupported document type declaration id '" + aDocumentTypeDeclarationId + "'");
        }
    }

    private void declareHTML5DocumentType() {
        declareDocumentType("html");
    }

    private void declareHTML401StrictDocumentType() {
        declareDocumentType("html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" " + "\"http://www.w3.org/TR/html4/strict.dtd\"");
    }

    private void declareHTML401TransitionalDocumentType() {
        declareDocumentType("html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" "
            + "\"http://www.w3.org/TR/html4/loose.dtd\"");
    }

    private void declareHTML401FramesetDocumentType() {
        declareDocumentType("html PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\" "
            + "\"http://www.w3.org/TR/html4/framset.dtd\"");
    }

    private void declareXHTML10StrictDocumentType() {
        declareDocumentType("html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
            + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"");
    }

    private void declareXHTML10TransitionalDocumentType() {
        declareDocumentType("html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
            + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"");
    }

    private void declareXHTML10FramesetDocumentType() {
        declareDocumentType("html PUBLIC \"-//W3C//DTD XHTML 1.0 Frameset//EN\" "
            + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd\"");
    }

    private void declareXHTML11DocumentType() {
        declareDocumentType("html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" "
            + "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\"");
    }

    @Override
    public void declareDocumentType(final String aDocumentTypeDeclaration) {
        dispatchXmlEvent(xmlEventFactory.createDocumentTypeDefinition(String.format("<!DOCTYPE %s>",
            aDocumentTypeDeclaration)));
    }

    @Override
    public void startElement(final String aLocalName) {
        startElement(xmlEventFactory.createQualifiedName(aLocalName));
    }

    @Override
    public void startElement(final String aNamespaceURI, final String aLocalName) {
        startElement(createQualifiedName(aNamespaceURI, aLocalName));
    }

    @Override
    public void startElement(final String aNamespaceURI, final String aLocalName, final String aPrefix) {
        startElement(createQualifiedName(aNamespaceURI, aLocalName, aPrefix));
    }

    @Override
    public void startElement(final XmlQualifiedName aXmlQualifiedName) {
        openElements.push(aXmlQualifiedName);
        dispatchXmlEvent(xmlEventFactory.createElementStart(aXmlQualifiedName));
    }

    @Override
    public void addAttribute(final String aLocalName, final String aValue) {
        addAttribute(xmlEventFactory.createQualifiedName(aLocalName), aValue);
    }

    @Override
    public void addAttribute(final String aNamespaceURI, final String aLocalName, final String aValue) {
        addAttribute(createQualifiedName(aNamespaceURI, aLocalName), aValue);
    }

    @Override
    public void addAttribute(final String aNamespaceURI, final String aLocalName, final String aPrefix,
        final String aValue)
    {
        dispatchXmlEvent(xmlEventFactory.createAttribute(aNamespaceURI, aLocalName, aPrefix, aValue));
    }

    @Override
    public void addAttribute(final XmlQualifiedName aXmlQualifiedName, final String aValue) {
        dispatchXmlEvent(xmlEventFactory.createAttribute(aXmlQualifiedName, aValue));
    }

    @Override
    public void addNamespace(final String aPrefix, final String aNamespaceURI) {
        dispatchXmlEvent(xmlEventFactory.createNamespace(aPrefix, aNamespaceURI));
    }

    @Override
    public void writeCharacters(final String aCharacters) {
        dispatchXmlEvent(xmlEventFactory.createCharacters(aCharacters));
    }

    @Override
    public void writeCharacters(final String aCharacters, final int aCharactersDataType) {
        dispatchXmlEvent(xmlEventFactory.createCharacters(aCharacters, aCharactersDataType));
    }

    @Override
    public void writeComment(final String aComment) {
        dispatchXmlEvent(xmlEventFactory.createComment(aComment));
    }

    @Override
    public void writeProcessingInstruction(final String aTarget, final String aData) {
        dispatchXmlEvent(xmlEventFactory.createProcessingInstruction(aTarget, aData));
    }

    @Override
    public void endElement() {
        dispatchXmlEvent(xmlEventFactory.createElementEnd(openElements.pop()));
    }

    @Override
    public void endDocument() {
        dispatchXmlEvent(xmlEventFactory.createDocumentEnd());
    }

    @Override
    public void startSourcing() {
    }
}
